AWS IoT SiteWise で複数デバイスのセンサーデータ統計値をグラフ表示してみた
前回の記事では各デバイスのセンサーデータのみを可視化していました。
しかし場合によっては、全デバイスのデータの平均値や最大値、ときには OEE(総合設備効率)なども取得して可視化したいことがあります。
そこで今回は、下位アセットモデルのデータの統計値を上位アセットのプロパティとして定義してダッシュボードに表示してみたいと思います。
前提
前提としてデバイスデータやダッシュボードは前回のものをそのまま使うことにします。
また、取得したい統計は「1 分あたりの全デバイスのクロック数の平均値」とします。
下位アセットモデルの修正とメトリクスの設定
今回は「クロック数」の平均値を取りたいので、下位モデルである各デバイスのクロック数の平均値を「メトリクス」プロパティとして取得します。
ここでいきなり余談ですが、わざわざ下位モデルで「メトリクス」プロパティを設定しなくても、上位モデル側で「下位モデルの測定プロパティ」を参照して平均計算すればいいのでは? という気がしますね。
しかし、階層をまたいでメトリクスに参照できるのは「下位モデルのメトリクス」もしくは「同階層の測定プロパティ」だけです。
そのため、今回のように下位モデルで「メトリクス」を定義しています。
再び少し本題からそれますが、メトリクスを設定する前に「クロック数の定義」を修正しておきたいと思います。
理由は「元のクロック数のデータ値では桁数が大きすぎてダッシュボードに表示した際にグラフ縦軸の目盛りが見切れてしまっていた」ためです。これを「MHz」に補正する為、「定義の変換」プロパティを使って取得したデータの桁と単位を下記の通り変更しました。
「定義の変換」が入力できたら、そのまま画面を下にスクロールして「メトリクス」を定義します。
下記のとおり「1 分間の各デバイスのクロック数の平均」を定義します。計算式はコピペできないので次のように入力します。
- 「計算式」欄に
avg()
と入力 avg()
のカッコの中にカーソルを移動- PCのキーボードの十字キーで「↓」をクリック
- プルダウンに出現する
clock_MHz
を選択
保存できたら「定義を変換する」タブを見て正しく設定が反映されていることを確認おきます。
「MHz への変換」が正しい計算式でセットされていることが確認できました。
「メトリクスの定義」タブでも正しい平均値の計算式でセットされていることが確認できました。
上位アセットモデルにメトリクスを設定
次に上位のアセットモデルである「All Device」にメトリクスを設定していきます。
「メトリクスの定義」の設定箇所で下記図のようにメトリクスを登録します。
SiteWise 独自の計算式の表記ですが、この計算式の意味は次のような意味を持ちます。
- 下位のアセットモデルのメトリクス
avg_clock
の平均値(1分間)を算出する - 下位のアセットモデルの特定には定義済みの階層定義である
device_hierarchy
を参照する
この計算式の設定が今回のポイントになります。
(設定方法はすぐ後に記載しています。)
さて、これは単純な計算式ではないので入力が少々難解ですが、私の環境(MacBook / Chrome)では下記の手順で入力できました。
- 計算式の入力エリアで PC のキーボードから十字キーの「↓」を入力
- プルダウンに現れる
device_hierarchy
を選択device_hierarchy
を選択しても計算式には何も入力されていないように見えてますが次の操作を行います。
- そのまま再度 PC のキーボードから十字キーの「↓」を入力
- プルダウンに現れる
avg_clock
を選択- この時点で計算式欄には
device_hierarchy
とavg_clock
が並んで入力されていれば OK です
- この時点で計算式欄には
- 次に計算式欄のカーソルを一番左に移動させて
avg(
をキーボードから直接入力 - 計算式欄の最後に
)
をキーボードから直接入力
ダッシュボードの修正
ここまでの設定が完了すれば、次はダッシュボードを修正します。
まず最初に、クロック数の桁が大きいままのグラフを削除しましょう。
次に単位を「MHz」に変換したプロパティ clock_MHz
をグラフに追加します。
下記では最初に test-device-1
のプロパティを追加しています。
同様に test-device-2
のプロパティを追加します。
最後に、この記事の本命である「2つのデバイスのクロック数の平均」をグラフに追加します。
上位アセットモデルで定義しているので、All Device
のアセットを選択して total_avg_clock
というプロパティをグラフの一番下に追加します。
最終的に下記のようなグラフになりました。一番下のグラフで「全デバイスのクロック数の平均」も可視化することができました。
最後に
今回は下位モデルのデータ郡から統計データを取得する方法を紹介しました。
記事中で赤文字でも記載しましたが、ポイントは「下位アセットモデルのプロパティを利用した計算式の設定」です。
コンソールから行うと非常に難解な作業になるので AWS CLI で設定したいところですが、その方法については改めて別の記事でご紹介したいと思います。
以上です。
補足
今回は分かりやすいグラフになるようにデバイスデータを生成するスクリプトを前回のものから少し変更しています。
(変更といってもグラフの山と谷になる時間を伸ばしただけです。)
コピペで使えるようにコードを記載しておきます。
import boto3 import uuid import random import time import logging import sys import json import math import threading logger = logging.getLogger() logger.setLevel(logging.INFO) streamHandler = logging.StreamHandler(stream=sys.stdout) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') streamHandler.setFormatter(formatter) logger.addHandler(streamHandler) client = boto3.client('iotsitewise') def batch_put_asset(device_id, sending_period): start = time.time() mode = 'low' # 最初はLOWモードでスタートすることにする logger.info("starting low mode... ") while True: timer = int(time.time() - start) # 現在時刻の取得 (小数部, 整数部) timestamp_float, timestamp_int = math.modf(time.time()) if mode == 'low': if timer < sending_period: MeasureValueTemp = random.uniform(1, 30) MeasureValueClock = random.randint(600000000,800000000) logger.info("temperature: {}".format(MeasureValueTemp)) logger.info("clock: {}".format(MeasureValueClock)) else: mode = 'high' logger.info("starting high mode... ") start = time.time() + 1 # reset timer continue else: #if mode == 'high': if timer < sending_period: # Mode: High load MeasureValueTemp = random.uniform(50, 90) MeasureValueClock = random.randint(1200000000,1500000000) logger.info("temperature: {}".format(MeasureValueTemp)) logger.info("clock: {}".format(MeasureValueClock)) else: mode = 'low' logger.info("starting low mode... ") start = time.time() + 1 # reset timer continue try: response = client.batch_put_asset_property_value( entries=[ { 'entryId': '{}'.format(uuid.uuid4()), 'propertyAlias': '/test/device/{}/temperature'.format(device_id), 'propertyValues': [ { 'value': { 'doubleValue': MeasureValueTemp }, 'timestamp': { 'timeInSeconds': int(timestamp_int), 'offsetInNanos': int(round((timestamp_float * 1000000000), 0)) }, 'quality': 'GOOD' }, ] }, { 'entryId': '{}'.format(uuid.uuid4()), 'propertyAlias': '/test/device/{}/clock'.format(device_id), 'propertyValues': [ { 'value': { 'doubleValue': MeasureValueClock }, 'timestamp': { 'timeInSeconds': int(timestamp_int), 'offsetInNanos': int(round((timestamp_float * 1000000000), 0)) }, 'quality': 'GOOD' }, ] }, ] ) logger.info("response: {}\n".format(json.dumps(response, indent=2))) logger.info("propertyAlias: {}\n".format(device_id)) if response['errorEntries']: logger.error("temperature: {} clock: {}".format(MeasureValueTemp, MeasureValueClock)) except Exception as e: logger.error("{}".format(e)) logger.error("temperature: {} clock: {}".format(MeasureValueTemp, MeasureValueClock)) time.sleep(5) device_1 = threading.Thread(target=batch_put_asset,args=(1,120)) device_2 = threading.Thread(target=batch_put_asset,args=(2,120)) device_1.start() device_2.start()